summaryrefslogtreecommitdiff
path: root/app/[lng]/admin/edp/components/project-selector.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]/admin/edp/components/project-selector.tsx')
-rw-r--r--app/[lng]/admin/edp/components/project-selector.tsx258
1 files changed, 258 insertions, 0 deletions
diff --git a/app/[lng]/admin/edp/components/project-selector.tsx b/app/[lng]/admin/edp/components/project-selector.tsx
new file mode 100644
index 00000000..68895cd1
--- /dev/null
+++ b/app/[lng]/admin/edp/components/project-selector.tsx
@@ -0,0 +1,258 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { Button } from '@/components/ui/button'
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Badge } from '@/components/ui/badge'
+import { Search, Check } from 'lucide-react'
+import {
+ ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+ SortingState,
+ ColumnFiltersState,
+ VisibilityState,
+ RowSelectionState,
+} from '@tanstack/react-table'
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table'
+import { getProjects } from '../actions/data-actions'
+import { toast } from 'sonner'
+
+interface Project {
+ id: number
+ code: string
+ name: string
+ type: string
+}
+
+interface ProjectSelectorProps {
+ selectedProject?: Project
+ onProjectSelect: (project: Project) => void
+ disabled?: boolean
+}
+
+export function ProjectSelector({ selectedProject, onProjectSelect, disabled }: ProjectSelectorProps) {
+ const [open, setOpen] = useState(false)
+ const [projects, setProjects] = useState<Project[]>([])
+ const [loading, setLoading] = useState(false)
+ const [sorting, setSorting] = useState<SortingState>([])
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
+ const [globalFilter, setGlobalFilter] = useState('')
+
+ const columns: ColumnDef<Project>[] = [
+ {
+ accessorKey: 'code',
+ header: '프로젝트 코드',
+ cell: ({ row }) => (
+ <div className="font-mono text-sm">{row.getValue('code')}</div>
+ ),
+ },
+ {
+ accessorKey: 'name',
+ header: '프로젝트명',
+ cell: ({ row }) => (
+ <div className="max-w-[200px] truncate">{row.getValue('name')}</div>
+ ),
+ },
+ {
+ accessorKey: 'type',
+ header: '타입',
+ cell: ({ row }) => (
+ <Badge variant="outline">{row.getValue('type')}</Badge>
+ ),
+ },
+ {
+ id: 'actions',
+ header: '선택',
+ cell: ({ row }) => (
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => handleProjectSelect(row.original)}
+ >
+ <Check className="h-4 w-4" />
+ </Button>
+ ),
+ },
+ ]
+
+ const table = useReactTable({
+ data: projects,
+ columns,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ onColumnVisibilityChange: setColumnVisibility,
+ onRowSelectionChange: setRowSelection,
+ onGlobalFilterChange: setGlobalFilter,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ state: {
+ sorting,
+ columnFilters,
+ columnVisibility,
+ rowSelection,
+ globalFilter,
+ },
+ })
+
+ const loadProjects = async () => {
+ setLoading(true)
+ try {
+ const result = await getProjects()
+ if (result.success) {
+ setProjects(result.data)
+ } else {
+ toast.error(result.error)
+ }
+ } catch (error) {
+ toast.error('프로젝트를 불러오는 중 오류가 발생했습니다.')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const handleProjectSelect = (project: Project) => {
+ onProjectSelect(project)
+ setOpen(false)
+ }
+
+ useEffect(() => {
+ if (open && projects.length === 0) {
+ loadProjects()
+ }
+ }, [open])
+
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button variant="outline" disabled={disabled} className="w-full justify-start">
+ {selectedProject ? (
+ <div className="flex items-center gap-2">
+ <span className="font-mono text-sm">[{selectedProject.code}]</span>
+ <span className="truncate">{selectedProject.name}</span>
+ </div>
+ ) : (
+ <span className="text-muted-foreground">프로젝트를 선택하세요</span>
+ )}
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-4xl max-h-[80vh]">
+ <DialogHeader>
+ <DialogTitle>프로젝트 선택</DialogTitle>
+ </DialogHeader>
+
+ <div className="space-y-4">
+ <div className="flex items-center space-x-2">
+ <Search className="h-4 w-4" />
+ <Input
+ placeholder="프로젝트 코드, 이름으로 검색..."
+ value={globalFilter}
+ onChange={(e) => setGlobalFilter(e.target.value)}
+ className="flex-1"
+ />
+ </div>
+
+ {loading ? (
+ <div className="flex justify-center py-8">
+ <div className="text-sm text-muted-foreground">프로젝트를 불러오는 중...</div>
+ </div>
+ ) : (
+ <div className="border rounded-md">
+ <Table>
+ <TableHeader>
+ {table.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id}>
+ {headerGroup.headers.map((header) => (
+ <TableHead key={header.id}>
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ </TableHead>
+ ))}
+ </TableRow>
+ ))}
+ </TableHeader>
+ <TableBody>
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+ <TableRow
+ key={row.id}
+ data-state={row.getIsSelected() && "selected"}
+ className="cursor-pointer hover:bg-muted/50"
+ onClick={() => handleProjectSelect(row.original)}
+ >
+ {row.getVisibleCells().map((cell) => (
+ <TableCell key={cell.id}>
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ </TableCell>
+ ))}
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell
+ colSpan={columns.length}
+ className="h-24 text-center"
+ >
+ 검색 결과가 없습니다.
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ )}
+
+ <div className="flex items-center justify-between">
+ <div className="text-sm text-muted-foreground">
+ 총 {table.getFilteredRowModel().rows.length}개 프로젝트
+ </div>
+ <div className="flex items-center space-x-2">
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ 이전
+ </Button>
+ <div className="text-sm">
+ {table.getState().pagination.pageIndex + 1} / {table.getPageCount()}
+ </div>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ 다음
+ </Button>
+ </div>
+ </div>
+ </div>
+ </DialogContent>
+ </Dialog>
+ )
+}